package org.jetnuke.modules.calendar.utils;

import org.apache.log4j.Logger;

import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import org.apache.velocity.VelocityContext;
import org.jetnuke.entities.CalendarEvent;

public class CalendarUtils {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger.getLogger(CalendarUtils.class);

	private CalendarUtils() {
	}

	public static void putCommonObjects(VelocityContext vc) {
		if (logger.isDebugEnabled()) {
			logger.debug("putCommonObjects(VelocityContext) - start"); //$NON-NLS-1$
		}

		Locale locale = (Locale) vc.get("_locale");
		String[] MONTHS = new DateFormatSymbols(locale).getMonths();
		vc.put("MONTHS", Arrays.asList(MONTHS));
		ArrayList years = new ArrayList();
		GregorianCalendar gc = new GregorianCalendar();
		int year = gc.get(Calendar.YEAR);
		for (int i = 0; i < 5; i++) {
			years.add(new Integer(year + i));
		}
		vc.put("next_years", years);
		String[] DAYS = new DateFormatSymbols(locale).getShortWeekdays();
		String[] dstArray = new String[7];
		System.arraycopy(DAYS, 1, dstArray, 0, 7);
		vc.put("DAYS", Arrays.asList(dstArray));

		if (logger.isDebugEnabled()) {
			logger.debug("putCommonObjects(VelocityContext) - end"); //$NON-NLS-1$
		}
	}

	public static List getActiveRecurringEvent(List calendarEvents,
			GregorianCalendar day_) {
		if (logger.isDebugEnabled()) {
			logger
					.debug("getActiveRecurringEvent(List, GregorianCalendar) - start"); //$NON-NLS-1$
		}

		List realRecurringEvents = new ArrayList();
		Iterator it = calendarEvents.iterator();
		while (it.hasNext()) {
			CalendarEvent ceTmp = (CalendarEvent) it.next();
			Date time = day_.getTime();
			if (ceTmp.getEXPIRATION() != null
					&& ceTmp.getEXPIRATION().before(time))
				continue;
			if (ceTmp.getSTART_DATE().after(time))
				continue;
			if (ceTmp.getRECURRING() == 1) {
				GregorianCalendar gcTmp = new GregorianCalendar();
				gcTmp.setTime(ceTmp.getSTART_DATE());
				if (ceTmp.getFREQ() == 1) { // Daily Freq and not expired
					realRecurringEvents.add(ceTmp);
				} else if (ceTmp.getFREQ() == 2) { // Only if same day of week
					if (gcTmp.get(Calendar.DAY_OF_WEEK) == day_
							.get(Calendar.DAY_OF_WEEK)) {
						realRecurringEvents.add(ceTmp);
					}
				} else if (ceTmp.getFREQ() == 3) { // Only if same day of month
					if (gcTmp.get(Calendar.DAY_OF_MONTH) == day_
							.get(Calendar.DAY_OF_MONTH)) {
						realRecurringEvents.add(ceTmp);
					}
				} else if (ceTmp.getFREQ() == 5) { // Only if same day of year
					if (gcTmp.get(Calendar.DAY_OF_MONTH) == day_
							.get(Calendar.DAY_OF_MONTH)
							&& gcTmp.get(Calendar.MONTH) == day_
									.get(Calendar.MONTH)) {
						realRecurringEvents.add(ceTmp);
					}
				} else if (ceTmp.getFREQ() == 6) { // Mon, Wed, Fri
					int dayOfWeek = gcTmp.get(Calendar.DAY_OF_WEEK);
					if (dayOfWeek == Calendar.MONDAY
							|| dayOfWeek == Calendar.WEDNESDAY
							|| dayOfWeek == Calendar.FRIDAY) {
						realRecurringEvents.add(ceTmp);
					}
				} else if (ceTmp.getFREQ() == 7) { // Tue, Thu
					int dayOfWeek = gcTmp.get(Calendar.DAY_OF_WEEK);
					if (dayOfWeek == Calendar.TUESDAY
							|| dayOfWeek == Calendar.THURSDAY) {
						realRecurringEvents.add(ceTmp);
					}
				} else if (ceTmp.getFREQ() == 8) { // Mon Thru Fri
					int dayOfWeek = gcTmp.get(Calendar.DAY_OF_WEEK);
					if (dayOfWeek == Calendar.MONDAY
							|| dayOfWeek == Calendar.THURSDAY
							|| dayOfWeek == Calendar.FRIDAY) {
						realRecurringEvents.add(ceTmp);
					}
				} else if (ceTmp.getFREQ() == 9) { // Sat ,Sun
					int dayOfWeek = gcTmp.get(Calendar.DAY_OF_WEEK);
					if (dayOfWeek == Calendar.SATURDAY
							|| dayOfWeek == Calendar.SUNDAY) {
						realRecurringEvents.add(ceTmp);
					}
				}

			} else if (ceTmp.getRECURRING() == 2) {
				int wf = ceTmp.getWF();
				// wf==1 First
				// wf==1 Second
				// wf==1 Third
				// wf==1 Fourth
				// wf==-1 Last
				int wd = ceTmp.getWD(); // Day of Week
				int wx = ceTmp.getWX();
				// 1" every month
				// 2 other month

				// 3 every 3 months
				// 4 every 4 months
				// 6 every 6 months
				// 7 every year
				if (day_.get(Calendar.DAY_OF_WEEK) != (wd + 1))
					continue;
				if (wf == -1)
					wf = 5;
				GregorianCalendar test = getNthOfMonth(wf, wd + 1, day_
						.get(Calendar.MONTH), day_.get(Calendar.YEAR));
				if (test.equals(day_)) {
					if (wx == 1) {
						realRecurringEvents.add(ceTmp);
					} else {
						GregorianCalendar gcTmp = new GregorianCalendar();
						gcTmp.setTime(ceTmp.getSTART_DATE());
						int month1 = day_.get(Calendar.MONTH);
						int month2 = gcTmp.get(Calendar.MONTH);
						if (wx == 2) {
							if (Math.abs(month1 - month2) % 2 == 0)
								realRecurringEvents.add(ceTmp);
						} else if (wx == 3) {
							if (Math.abs(month1 - month2) % 3 == 0)
								realRecurringEvents.add(ceTmp);
						} else if (wx == 4) {
							if (Math.abs(month1 - month2) % 4 == 0)
								realRecurringEvents.add(ceTmp);
						} else if (wx == 6) {
							if (Math.abs(month1 - month2) % 6 == 0)
								realRecurringEvents.add(ceTmp);
						} else if (wx == 7) {
							if (month1 == month2)
								realRecurringEvents.add(ceTmp);
						}
					}
				}

			}
		}

		if (logger.isDebugEnabled()) {
			logger
					.debug("getActiveRecurringEvent(List, GregorianCalendar) - end"); //$NON-NLS-1$
		}
		return realRecurringEvents;
	}

	// Inspired by no.geosoft.cc.util.Day class
	public static GregorianCalendar getNthOfMonth(int n, int dayOfWeek,
			int month, int year) throws ArrayIndexOutOfBoundsException {
		if (logger.isDebugEnabled()) {
			logger.debug("getNthOfMonth(int, int, int, int) - start"); //$NON-NLS-1$
		}

		// Validate the dayOfWeek argument
		if (dayOfWeek < 1 || dayOfWeek > 7)
			throw new ArrayIndexOutOfBoundsException(dayOfWeek);

		GregorianCalendar first = new GregorianCalendar(year, month, 1);

		int offset = dayOfWeek - first.get(Calendar.DAY_OF_WEEK);
		if (offset < 1)
			offset = 7 + offset;

		int dayNo = (n - 1) * 7 + offset + 1;

		GregorianCalendar returnGregorianCalendar = dayNo > first
				.getActualMaximum(Calendar.DAY_OF_MONTH) ? null
				: new GregorianCalendar(year, month, dayNo);
		if (logger.isDebugEnabled()) {
			logger.debug("getNthOfMonth(int, int, int, int) - end"); //$NON-NLS-1$
		}
		return returnGregorianCalendar;
	}

	public static GregorianCalendar getFirstOfMonth(int dayOfWeek, int month,
			int year) {
		if (logger.isDebugEnabled()) {
			logger.debug("getFirstOfMonth(int, int, int) - start"); //$NON-NLS-1$
		}

		GregorianCalendar returnGregorianCalendar = getNthOfMonth(1, dayOfWeek,
				month, year);
		if (logger.isDebugEnabled()) {
			logger.debug("getFirstOfMonth(int, int, int) - end"); //$NON-NLS-1$
		}
		return returnGregorianCalendar;
	}

	public static GregorianCalendar getLastOfMonth(int dayOfWeek, int month,
			int year) {
		if (logger.isDebugEnabled()) {
			logger.debug("getLastOfMonth(int, int, int) - start"); //$NON-NLS-1$
		}

		GregorianCalendar day = getNthOfMonth(5, dayOfWeek, month, year);
		GregorianCalendar returnGregorianCalendar = day != null ? day
				: getNthOfMonth(4, dayOfWeek, month, year);
		if (logger.isDebugEnabled()) {
			logger.debug("getLastOfMonth(int, int, int) - end"); //$NON-NLS-1$
		}
		return returnGregorianCalendar;
	}

	public static GregorianCalendar getFirstDayOfWeek(int year, int week) {
		if (logger.isDebugEnabled()) {
			logger.debug("getFirstDayOfWeek(int, int) - start"); //$NON-NLS-1$
		}

		GregorianCalendar firstDayWeek = new GregorianCalendar();
		firstDayWeek.set(Calendar.YEAR, year);
		firstDayWeek.set(Calendar.WEEK_OF_YEAR, week);
		firstDayWeek.set(Calendar.DAY_OF_WEEK, 1);
		firstDayWeek.set(Calendar.HOUR_OF_DAY, 0);
		firstDayWeek.set(Calendar.MINUTE, 0);
		firstDayWeek.set(Calendar.SECOND, 0);
		firstDayWeek.set(Calendar.MILLISECOND, 0);

		if (logger.isDebugEnabled()) {
			logger.debug("getFirstDayOfWeek(int, int) - end"); //$NON-NLS-1$
		}
		return firstDayWeek;
	}

	public static GregorianCalendar getLastDayOfWeek(int year, int week) {
		if (logger.isDebugEnabled()) {
			logger.debug("getLastDayOfWeek(int, int) - start"); //$NON-NLS-1$
		}

		GregorianCalendar firstDayWeek = new GregorianCalendar();
		firstDayWeek.set(Calendar.YEAR, year);
		firstDayWeek.set(Calendar.WEEK_OF_YEAR, week);
		firstDayWeek.set(Calendar.DAY_OF_WEEK, 7);
		firstDayWeek.set(Calendar.HOUR_OF_DAY, 0);
		firstDayWeek.set(Calendar.MINUTE, 0);
		firstDayWeek.set(Calendar.SECOND, 0);
		firstDayWeek.set(Calendar.MILLISECOND, 0);

		if (logger.isDebugEnabled()) {
			logger.debug("getLastDayOfWeek(int, int) - end"); //$NON-NLS-1$
		}
		return firstDayWeek;
	}

	public static List getDaysOfWeek(int year, int week) {
		if (logger.isDebugEnabled()) {
			logger.debug("getDaysOfWeek(int, int) - start"); //$NON-NLS-1$
		}

		List list = new ArrayList();
		GregorianCalendar firstDayWeek = getFirstDayOfWeek(year, week);
		list.add(firstDayWeek);

		for (int i = 0; i < 6; i++) {
			GregorianCalendar tmp = new GregorianCalendar();
			tmp.setTimeInMillis(firstDayWeek.getTimeInMillis());
			tmp.add(Calendar.DATE, 1 + i);
			list.add(tmp);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("getDaysOfWeek(int, int) - end"); //$NON-NLS-1$
		}
		return list;
	}

	public static void main(String args[]) {
		if (logger.isDebugEnabled()) {
			logger.debug("main(String[]) - start"); //$NON-NLS-1$
		}

		System.out.println(getNthOfMonth(1, Calendar.WEDNESDAY,
				Calendar.FEBRUARY, 2006).getTime());
		System.out.println(getLastOfMonth(Calendar.SUNDAY, Calendar.DECEMBER,
				2006).getTime());

		GregorianCalendar jan10 = new GregorianCalendar(2006,
				Calendar.FEBRUARY, 1);
		int week = jan10.get(Calendar.WEEK_OF_YEAR);
		int dayweek = jan10.get(Calendar.DAY_OF_WEEK);
		if (dayweek > 1)
			jan10.add(Calendar.DATE, -dayweek + 1);
		System.out.println(jan10.getTime());
		System.out.println(getFirstDayOfWeek(2006, 1).getTime());

		if (logger.isDebugEnabled()) {
			logger.debug("main(String[]) - end"); //$NON-NLS-1$
		}
	}

	public static List getDaysOfMonth(int year, int month) {
		if (logger.isDebugEnabled()) {
			logger.debug("getDaysOfMonth(int, int) - start"); //$NON-NLS-1$
		}

		List list = new ArrayList();
		GregorianCalendar firstDayMonth = getFirstDayOfMonth(year, month);
		list.add(firstDayMonth);
		int max = firstDayMonth.getActualMaximum(Calendar.DAY_OF_MONTH);
		for (int i = 0; i < max - 1; i++) {
			GregorianCalendar tmp = new GregorianCalendar();
			tmp.setTimeInMillis(firstDayMonth.getTimeInMillis());
			tmp.add(Calendar.DATE, 1 + i);
			list.add(tmp);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("getDaysOfMonth(int, int) - end"); //$NON-NLS-1$
		}
		return list;
	}

	private static GregorianCalendar getFirstDayOfMonth(int year, int month) {
		if (logger.isDebugEnabled()) {
			logger.debug("getFirstDayOfMonth(int, int) - start"); //$NON-NLS-1$
		}

		GregorianCalendar firstDayMOnth = new GregorianCalendar();
		firstDayMOnth.set(Calendar.YEAR, year);
		firstDayMOnth.set(Calendar.MONTH, month - 1);
		firstDayMOnth.set(Calendar.DAY_OF_MONTH, 1);
		firstDayMOnth.set(Calendar.HOUR_OF_DAY, 0);
		firstDayMOnth.set(Calendar.MINUTE, 0);
		firstDayMOnth.set(Calendar.SECOND, 0);
		firstDayMOnth.set(Calendar.MILLISECOND, 0);

		if (logger.isDebugEnabled()) {
			logger.debug("getFirstDayOfMonth(int, int) - end"); //$NON-NLS-1$
		}
		return firstDayMOnth;
	}
}
